package com.szwistar.common.os;

import com.szwistar.common.datastruct.Utils;
import org.apache.log4j.PropertyConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.Signal;
import sun.misc.SignalHandler;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

public class Os {
	protected static final Logger LOG = LoggerFactory.getLogger(Os.class);

	// JDK 版本号: "1.6" ==> 1006
	protected static Integer jdkVersion = null;
	// 操作系统类型
	protected static OSType osType = OSType.Unkown;

	/**
	 * 获取兼容的 JDK 版本
	 * @return JDK 版本号: "1.6" ==> 1006
	 */
	public static int getJDKVersion() {
		if(jdkVersion != null) { return jdkVersion; }
		String ver = System.getProperty("java.version");
		String[] vers = Utils.match("(\\d+)\\.(\\d+)", ver);
		int v1 = Integer.parseInt(vers[1]);
		int v2 = Integer.parseInt(vers[2]);
		return jdkVersion = v1*1000 + v2;
	}

	/**
	 * 操作系统类型
	 */
	public static enum OSType {
		Unkown("Unkown"),
		Windows("Windows"), Android("Android"), Linux("Linux"), Mac_OS("Mac OS"), Mac_OS_X("Mac OS X"), OS2("OS/2"), OS390("OS/390"),
		Solaris("Solaris"), SunOS("SunOS"), MPEiX("MPE/iX"), HP_UX("HP-UX"), AIX("AIX"), FreeBSD("FreeBSD"),
		Irix("Irix"), Digital_Unix("Digital Unix"), NetWare_411("NetWare"), OSF1("OSF1"), OpenVMS("OpenVMS");

		private String name;
		private OSType(String name) { this.name = name; }
		public String getName() { return name; }
	}

	/**
	 * 取得操作系统类型
	 */
	public static OSType getOSType() { return osType; }

	/*
	 * 是否是 Windows 系列操作系统
	 */
	public static boolean isWindowsFamily() { return osType == OSType.Windows; }

	/*
	 * 是否是 Unix 系列操作系统
	 */
	public static boolean isUnixFamily() {
		return  osType == OSType.Android || osType == OSType.Linux || osType == OSType.FreeBSD ||
				osType == OSType.Mac_OS || osType == OSType.Mac_OS_X ||
				osType == OSType.Solaris || osType == OSType.SunOS || osType == OSType.HP_UX ||
				osType == OSType.Digital_Unix;
	}

	static {
		String OS = System.getProperty("os.name").toLowerCase();
		/**/ if(OS.contains("windows")) { osType = OSType.Windows; }
		else if(OS.contains("linux")) { osType = OSType.Linux; }
		else if(OS.contains("mac") && OS.contains("os") && OS.contains("x")) { osType = OSType.Mac_OS_X; }
		else if(OS.contains("mac") && OS.contains("os")) { osType = OSType.Mac_OS; }
		else if(OS.contains("os/2")) { osType = OSType.OS2; }
		else if(OS.contains("solaris")) { osType = OSType.Solaris; }
		else if(OS.contains("sunos")) { osType = OSType.SunOS; }
		else if(OS.contains("mpe/ix")) { osType = OSType.MPEiX; }
		else if(OS.contains("hp-ux")) { osType = OSType.HP_UX; }
		else if(OS.contains("aix")) { osType = OSType.AIX; }
		else if(OS.contains("os/390")) { osType = OSType.OS390; }
		else if(OS.contains("freebsd")) { osType = OSType.FreeBSD; }
		else if(OS.contains("irix")) { osType = OSType.Irix; }
		else if(OS.contains("digital") && OS.contains("unix")) { osType = OSType.Digital_Unix; }
		else if(OS.contains("netware")) { osType = OSType.NetWare_411; }
		else if(OS.contains("osf1")) { osType = OSType.OSF1; }
		else if(OS.contains("openvms")) { osType = OSType.OpenVMS; }
		else {
			try { Class.forName("android.app.Application"); osType = OSType.Android; } catch (Exception e) {}
		}
	}

	/**
	 * 获取进程 PID
	 * @return <=0 错误；>0 进程ID
	 */
    public static Long getPid() {
        try {
            // 兼容java7
            if (Class.forName("java.lang.management.ManagementFactory") != null) {
                Object runtimeMXBean = Class.forName("java.lang.management.ManagementFactory").getMethod("getRuntimeMXBean").invoke(null);
                Method method = runtimeMXBean.getClass().getMethod("getName");
                method.setAccessible(true);
                return Long.valueOf(((String) method.invoke(runtimeMXBean)).split("@")[0]);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 把当前进程 PID 写入文件，退出时自动删除
     * @param file 指定文件完整路径
     * @param failOnExists true: 如果文件已经存，则抛出异常；false: 如果已经存在，则覆盖
     * @return PID 文件
     */
    public static File createPidFile(File file, boolean failOnExists) throws Exception {
    	Long pid = getPid();
    	if(pid == null) {
    		throw new RuntimeException("获取进程PID失败！");
    	}

    	if(file.exists() && failOnExists) {
    		// 文件已经存，则抛出异常
    		throw new RuntimeException("打开进程PID文件失败！文件已存在！" + file);
    	}

    	// 写入进程 PID
    	FileUtils.writeFile(file, pid.toString());
    	// 设置文件退出时删除
    	file.deleteOnExit();

    	return file;
    }

    /**
     * 命令行执行结果
     */
	public static class CmdResult {
		// 错误码
		public int code;
		// stdout
		public String stdout;
		// stderr
		public String stderr;

		public CmdResult(int code) { this.code = code; }
		public CmdResult(int code, String stdout, String stderr) {
			this(code);
			this.stdout = stdout;
			this.stderr = stderr;
		}
	}

	/**
	 * 以 root 权限执行命令行，并返回 stdout + stderr 输出
	 * @param timeout >0 表示命令执行超时时间(ms); <=0 表示无限等待
	 * @param cmd 命令行字符串
	 * @param args 命令行字符串格式化参数
	 * @return 返回 stdout + stderr 输出
	 */
	public static CmdResult exec(int timeout, String cmd, Object ...args) {
		if(Utils.isNotEmpty(args)) { cmd = String.format(cmd, args); }

		LOG.warn("执行命令行：{}", cmd);
		CmdResult res = exec(new String[] {cmd}, timeout, true, true);
		if(res.stdout != null) { LOG.debug("{}", res.stdout); }
		if(res.code != 0 && res.stderr != null) { LOG.error("{}", res.stderr); }
		return res;
	}

	/**
	 * 执行命令行
	 * @param cmds 命令行数组
	 * @param asRoot 是否以 root 权限运行
	 * @param needResult 是否需要输出
	 * @return <ul>
	 * <li>如果 needResult == false, 则 {@link CmdResult#stdout} = null, {@link CmdResult#stderr} = null.</li>
	 * <li>如果 {@link CmdResult#code} == -1, 表示执行过程中可能遇到错误。</li>
	 * </ul>
	 */
	public static CmdResult exec(String[] cmds, int timeout, boolean asRoot, boolean needResult) {
		int code = -1;
		if(Utils.isEmpty(cmds)) { return new CmdResult(code, null, "命令为空！"); }

		Process proc = null;
		DataOutputStream os = null;
		BufferedReader stdout = null;
		BufferedReader stderr = null;
		StringBuilder strout = null;
		StringBuilder strerr = null;
		String lineEnd = "\n";

		try {
			if(isWindowsFamily()) {
				// Windows 环境
				// Windows 上只能一次执行一行
				if(cmds.length > 1) { return new CmdResult(code, null, "无法执行多条命令"); }
				if(Utils.isEmpty(cmds[0])) { return new CmdResult(code, null, "命令为空！"); }
				// 创建 shell 进程
				proc = Runtime.getRuntime().exec("cmd /c " + cmds[0]);
				lineEnd = "\r\n";
			} else {
				// Unix/Linux 环境
				// 创建 shell 进程
				proc = Runtime.getRuntime().exec(asRoot ? "su" : "sh");
				// 取得 shell 进程的 stdin 流
				os = new DataOutputStream(proc.getOutputStream());

				// 通过 stdin 逐条输入命令执行
				for(String cmd : cmds) {
					if(Utils.isEmpty(cmd)) { continue; }
					os.write(cmd.getBytes()); // 不要使用 os.writeBytes(commmand), 以避免中文字符错误
					os.writeBytes("\n"); os.flush();
				}

				// 命令 shell 退出
				os.writeBytes("exit\n"); os.flush();
			}

			if(getJDKVersion() >= 1008) {
				// 等待 shell 退出完成
				if (proc.waitFor(timeout, TimeUnit.MILLISECONDS)) {
					// 取得错误码
					code = proc.exitValue();
				} else {
					code = -2;
					LOG.error("命令执行超时！强制退出！");
					proc.destroy();
				}
			} else {
				// 等待 shell 退出完成，并取得错误码
				code = proc.waitFor();
			}

			// 如果需要，则取得 shell 的输出
			if(needResult) {
				strout = new StringBuilder();
				strerr = new StringBuilder();
				stdout = new BufferedReader(new InputStreamReader(proc.getInputStream()));
				stderr = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
				String line;
				while ((line = stdout.readLine()) != null) {
					strout.append(line); strout.append(lineEnd);
				}
				while ((line = stderr.readLine()) != null) {
					strerr.append(line); strout.append(lineEnd);
				}
				if(code == -2) {
					strerr.append("命令执行超时！"); strout.append(lineEnd);
				}
			}

			// 正常返回
			return new CmdResult(code, (strout == null) ? null : strout.toString(), (strerr == null) ? null : strerr.toString());
		} catch (Exception e) {
			// 出错
			return new CmdResult(-1, null, e.toString());
		} finally {
			// 无论如何都要释放资源
			try { if(os != null) { os.close(); } } catch (Exception e) {}
			try { if(stdout != null) { stdout.close(); } } catch (Exception e) {}
			try { if(stderr != null) { stderr.close(); } } catch (Exception e) {}
			if(proc != null) { proc.destroy(); }
		}
	}

	public static interface SignalHandler2 {
		public void handle(String signal);
	}

	public static void onSignal(String signal, final SignalHandler2 handler2) {
		SignalHandler handler = new SignalHandler() {
			@Override public void handle(Signal sig) {
				handler2.handle(sig.getName());
			}
		};
		Signal.handle(new Signal(signal), handler);
	}

	/**
	 * 不产生异常地休眠
	 */
	public static boolean sleep(long millis) {
        try {
			Thread.sleep(millis);
			return true;
		} catch (InterruptedException e) {
			return false;
		}
	}

	/**
	 * 判断类是否是在 jar 包中
	 */
	public static boolean isInJar(Class<?> clazz) {
		File file = new File(clazz.getProtectionDomain().getCodeSource().getLocation().getPath());
		return file.isFile();
	}

	// for test only
	public static void test1() throws Exception {
		FileUtils.writeFile(new File("test.pid"), String.format("%d", getPid()));
		System.out.println( exec(1000, "adb devices") );
		System.out.println( exec(1000, "adb xxx") );
		System.out.println( exec(1000, "dir /w") );
	}

	public static void test2() {
		System.out.println( isInJar(Os.class) );
	}

	public static void main(String[] args) {
		PropertyConfigurator.configure("assets/conf/log4j.properties");
		System.out.println( getOSType() );
		System.out.println( System.getProperty("os.version") );
		System.out.println( System.getProperty("os.arch") );
		test2();
	}
}
